iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
自我挑戰組

React 個人讀書會系列 第 28

Day 28 - 記憶值和函式:useMemo、useCallback

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231012/20103817GLuqY8e9AQ.jpg

useMemo 和 useCallback 要解決什麼?

在昨天的文章裡,我們最後有遇到一個問題,當我們傳入的 props 是一個物件時,元件在重新渲染的時候,這些值都會被重新建立,所以被視為不同的 props,導致重新渲染,這時候我們可以使用 React 提供的 useMemouseCallback 來處理。

import { useState, memo } from "react";

function App() {
  const [count, setCount] = useState(0);

  // 每次 App 重新渲染的時候,都會重新建立這一個物件
  const archiveOptions = {
    show: false,
    title: "Post archive"
  };

  return (
    <section>
      <h2>{count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>++</button>
      // archiveOptions 被重新建立,導致 Archive 重新渲染
      <Archive archiveOptions={archiveOptions} />
    </section>
  );
}

const Archive = memo(function Archive({ show, title }) {
  console.log(show);

  return <h1>{title}</h1>;
});

useMemo

useMemo 是 React 提供的一個 Hook,主要用於記憶住我們的值,讓它們不會在每次渲染的時候都重新建立,使用方法是傳入一個 callback function,並且最後返回一個值。

import { useState, useMemo, memo } from "react";

function App() {
  ...

  const archiveOptions = useMemo(() => {
    return {
      show: false,
      title: 'Post archive',
    };
  });

  return (
    ...
  );
}

useMemouseEffect 一樣有一個依賴陣列,主要用途是當其中的依賴項發生變化時,React 會幫我們重新建立這一個值,確保可以跟狀態保持同步。

import { useState, memo } from "react";

function App() {
  const [posts, setPosts] = useState([]);
  const [count, setCount] = useState(0);

  const archiveOptions = useMemo(() => {
    return {
      show: false,
      title: `Post archive in addition to ${posts.length} main posts`,
    };
  }, [posts.length]);

  return (
    <section>
      <h2>{count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>++</button>
      <Archive archiveOptions={archiveOptions} />
    </section>
  );
}

const Archive = memo(function Archive({ show, title }) {
  console.log(show);

  return <h1>{title}</h1>;
});

useCallback

useCallback 在用法上和 useMemo 十分類似,差別在於 useCallback 要記憶住的是函式,因為每個函式在重新渲染的時候也會被重新建立,導致子元件重新渲染的問題。

import { useState, memo } from "react";

function App() {
  ...

  // 每次 App 重新渲染的時候,都會重新建立這一個函式
  function handleAddPost(post) {
    setPosts((posts) => [post, ...posts]);
  });

  return (
    <section>
      <h2>{count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>++</button>
      // handleAddPost 被重新建立,導致 Archive 重新渲染
      <Archive
        archiveOptions={archiveOptions}
        onAddPost={handleAddPost}
      />
    </section>
  );
}

使用方法是將函式放入 useCallback 裡面,再重新賦予一個變數,useCallback 也會也有一個依賴陣列,去告訴 React 何時需要重新建立函式。

import { useState, memo } from "react";

function App() {
  ...

  const handleAddPost = useCallback(function handleAddPost(post) {
    setPosts((posts) => [post, ...posts]);
  }, []);

  return (
    <section>
      <h2>{count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>++</button>
      <Archive
        archiveOptions={archiveOptions}
        onAddPost={handleAddPost}
      />
    </section>
  );
}

這邊有一個有趣的點,當我們傳入的 props 是一個 setter 函式,會發現 Archive 不會重新渲染,這是因為 React 確保了 useState 的 setter 函式始終具有穩定的身份,這意味著它們不會在渲染時發生變化(可以把 setter 函式想像成已經自動記憶化)。

import { useState, memo } from "react";

function App() {
  const [posts, setPosts] = useState([]);
  const [count, setCount] = useState(0);

  return (
    <section>
      <h2>{count}</h2>
      <button onClick={() => setCount((count) => count + 1)}>++</button>
      <Archive setPosts={setPosts} />
    </section>
  );
}

結語

當元件重新渲染的時候,元件內部所有的值都會重新建立(包括物件、函式),因此,假設我們傳入 memo 元件的 props 是物件或函式,還是會觸發它的重新渲染,因為每次的 props 都被視為一個新的值,所以我們可以使用 useMemo 記憶值、useCallback 記憶函式,並傳入一個依賴陣列,告訴 React 當依賴項發生變化的時候,幫我們重新建立新的值。


上一篇
Day 27 - 記憶元件:memo
下一篇
Day 29 - 共用狀態管理:Redux
系列文
React 個人讀書會30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言